package service

import (
	"github.com/looplab/fsm"
	"github.com/syndtr/goleveldb/leveldb"
	"gitee.com/thomasgw/gms/common"
	"github.com/syndtr/goleveldb/leveldb/util"
	"fmt"
	"sync"
	"time"
)

type GC struct {
	stateMachine *fsm.FSM
	db           *leveldb.DB
	lock         *sync.Mutex
}

func NewGC(db *leveldb.DB, lock *sync.Mutex) *GC{
	gcEngine := &GC{
		db:   db,
		lock: lock,
	}
	//gcEngine.initState()
	return gcEngine
}

func (gc *GC) initState() {
	gc.stateMachine = fsm.NewFSM(
		STATE_CREATE,
		fsm.Events{
			{Name: EVENT_START, Src: []string{STATE_CREATE}, Dst: STATE_NORMAL},
			{Name: EVENT_GC, Src : []string{STATE_NORMAL}, Dst: STATE_GC},
			{Name: EVENT_GCFINISH, Src: []string{STATE_GC}, Dst: STATE_NORMAL},
		},
		fsm.Callbacks{
			"enter_" + STATE_NORMAL: func(e *fsm.Event) {
				gc.normalListening(e)
			},
			"enter_" + STATE_GC: func(e *fsm.Event) {
				gc.enterGC(e)
			},
		},
	)
}

// process function
func (gc *GC) normalListening(e *fsm.Event) {
	timer := time.NewTimer(GC_TRIGGER_TIME)
	go func(){
		<- timer.C
		// STW
		fmt.Printf("[GC] trigger gc \n")
		gc.stateMachine.Event(EVENT_GC)
		timer.Stop()
		return
	}()
}

func (gc *GC) enterGC(e *fsm.Event) {
	// process data clear
	value, err := gc.getCursorValue()
	if err == nil {
		go func() {
			gc.stateMachine.Event(EVENT_GCFINISH)
			fmt.Printf("[GC] start concurrent sweep \n")
			ite := gc.db.NewIterator(&util.Range{
				Start: common.EventPrefix,
				Limit: value,
			}, nil)
			for ite.Next() {
				s := make([]byte, len(ite.Key()))
				copy(s, ite.Key())
				err = gc.db.Delete(s, nil)
				fmt.Printf("[GC] delete key: %v \n", string(s))
				if err != nil {
					fmt.Printf("[GC] delete key : %v error: %v \n", string(s), err.Error())
					break
				}
			}
			fmt.Printf("[GC] sweep finish \n")
			ite.Release()
		}()
	} else {
		go func(){
			fmt.Printf("get cursor key error: %v \n", err.Error())
			gc.stateMachine.Event(EVENT_GCFINISH)
			fmt.Printf("[GC] switch state to 'Normal' \n")
		}()
	}
}

func (gc *GC) getCursorValue() ([]byte, error) {
	gc.lock.Lock()
	defer gc.lock.Unlock()
	fmt.Printf("[GC] enter mark stage \n")
	return gc.db.Get(common.CursorKey, nil)
}
